#include <iostream>
#include <cstdlib>
#include <assert.h>
#include <string.h>
#include <math.h>
#include <list>
#include <vector>
#include <malloc.h>

#include "../include/util.h"

int attcmp(const Attribute &a, const Attribute &b) {
    switch (a.type) {
        case kShort:
            if (a.short_value < b.short_value)
                return -1;
            else if (a.short_value > b.short_value)
                return 1;
            break;
        case kInt:
            if (a.int_value < b.int_value)
                return -1;
            else if (a.int_value > b.int_value)
                return 1;
            break;
        case kVarchar:
            return strcmp(a.char_value, b.char_value);
    }
    return 0;
}

int keycmp(const Key &a, const Key &b) {
    assert(a.attribute_count == b.attribute_count);

    // Compare the keys
    for (int i = 0; i < a.attribute_count; i++) {
        assert(a.value[i] != NULL);
        assert(b.value[i] != NULL);
        int res = attcmp(*(a.value[i]), *(b.value[i]));
        if (res != 0)
            return res;
    }
    return 0;
}

/**
 * Checks whether every attribute of key a is smaller than or equal to key b
 */
bool CheckBounds(const Key &a, const Key &b, int *index = NULL) {
    assert(a.attribute_count == b.attribute_count);

    for (int i = 0; i < a.attribute_count; i++) {
        if ((a.value[i] != NULL) && (b.value[i] != NULL)
                && (attcmp(*(a.value[i]), *(b.value[i])) > 0)) {
            if (index)
                *index = i;
            return false;
        }
    }
    return true;
}

void CopyKey(Key &a, const Key &b) {
    a.value = new Attribute*[b.attribute_count];
    a.attribute_count = b.attribute_count;
    for (int i = 0; i < a.attribute_count; i++) {
        if (b.value[i] != NULL) {
            a.value[i] = new Attribute();
            memcpy(a.value[i], b.value[i], sizeof (Attribute));
        } else {
            a.value[i] = NULL;
        }
    }
}

void CopyRecord(Record *dst, Record *src) {
    dst->payload.size = src->payload.size;

    try {
        free(dst->payload.data);
    } catch (...) {
    }

    dst->payload.data = malloc((int) src->payload.size);
    memcpy(dst->payload.data, src->payload.data, (int) src->payload.size);
    CopyKey(dst->key, src->key);
}

void CopyRecord(Record *dst, Key key, Block &payload) {
    dst->payload.size = payload.size;

    try {
        free(dst->payload.data);
    } catch (...) {
    }

    dst->payload.data = malloc((int) payload.size);
    memcpy(dst->payload.data, payload.data, (int) payload.size);
    CopyKey(dst->key, key);
}

/**
 * Deletes a key
 */
void DeleteKey(Key* key) {
    //if (key.value == 0)
    //return;
    for (int i = 0; i < key->attribute_count; i++) {
        if (key->value[i] != NULL) {
            delete key->value[i];
        }
    }

    delete[] key->value;
}

/**
 *  Deletes a record
 */
void DeleteRecord(Record **record) {
    if (*record == NULL)
        return;

    DeleteKey(&((*record)->key));

    free((*record)->payload.data);
    delete (*record);

    (*record) = NULL;
}

void printValue(Record *record) {
    int num = record->key.attribute_count;
    for (int i = 0; i < num; i++) {
        switch (record->key.value[i]->type) {
            case kShort:
                std::cout << record->key.value[i]->short_value;
                break;
            case kInt:
                std::cout << record->key.value[i]->int_value;
                break;
            case kVarchar:
                std::cout << record->key.value[i]->char_value;
                break;
        }
        std::cout << " ";
    }

    std::cout << (char *) record->payload.data;
    std::cout << std::endl;
}

/**
 * Quad Tree Comparing Way bit by bit
 */
int attcmp_(const Attribute &a, const Attribute &b) {
    switch (a.type) {
        case kShort:
            if (a.short_value <= b.short_value)
                return 0;
            else if (a.short_value > b.short_value)
                return 1;
            break;
        case kInt:
            if (a.int_value <= b.int_value)
                return 0;
            else if (a.int_value > b.int_value)
                return 1;
            break;
        case kVarchar:
            int res = strcmp(a.char_value, b.char_value);
            if (res == -1)
                res = 0;
            return res;
    }
    return 0;
}

uint32_t keycmp_qtree(const Key &a, const Key &b) {
    assert(a.attribute_count == b.attribute_count);
    uint32_t difference = 0;

    uint8_t count = (a.attribute_count > 4 ? 4 : a.attribute_count);
    // Compare the keys
    for (int i = 0; i < count; i++) {
        assert(a.value[i] != NULL);
        assert(b.value[i] != NULL);

        int res = attcmp_(*(a.value[i]), *(b.value[i]));
        int power = (int) pow((double) 2, a.attribute_count - 1 - i);
        difference += (res * power);
    }
    return difference;
}

int BlockCmp(const Block &a, const Block &b) {
    if (a.size == b.size) {
        int res = memcmp(&a, &b, (int) a.size);
        if (res > 0)
            return 1;
        if (res < 0)
            return -1;
        return 0;
    }
    if (a.size > b.size)
        return 1;
    return -1;
}

/**
 * Compares two records; a>b => 1, a<b => -1, a==b => 0
 */
int recordCmp(const Record &a, const Record &b) {
    if (keycmp(a.key, b.key) == 0)
        return BlockCmp(a.payload, b.payload);
    return keycmp(a.key, b.key);
}

void CopyPayload(Block &dstPayload, Block &srcPayload) {
    dstPayload.size = srcPayload.size;
    dstPayload.data = malloc((int) srcPayload.size);
    memcpy(dstPayload.data, srcPayload.data, (int) srcPayload.size);
}

int keycmp_range(const Key &a, const Key &mx, const Key &mn) {
    assert(a.attribute_count == mx.attribute_count == mn.attribute_count);
    for (int i = 0; i < a.attribute_count; i++) {
        assert(a.value[i] != NULL);
        assert(mx.value[i] != NULL);
        assert(mn.value[i] != NULL);
        int res = attcmp(*(a.value[i]), *(mn.value[i]));
        if (res < 0)
            return 5;
        res = attcmp(*(a.value[i]), *(mx.value[i]));
        if (res > 0)
            return 5;
    }
    return 0;
}

/**
 * Create a new attribute of type SHORT
 */
Attribute *ShortAttribute(int32_t value) {
    Attribute *attribute = new Attribute;
    attribute->type = kShort;
    attribute->short_value = value;
    /*attribute->char_value=NULL;
     attribute->int_value=NULL;*/
    return attribute;
}

/**
 *  Create a new attribute of type INT
 */
Attribute *IntAttribute(int64_t value) {
    Attribute *attribute = new Attribute;
    attribute->type = kInt;
    attribute->int_value = value;
    return attribute;
}

/**
 *  Create a new attribute of type VARCHAR
 */
Attribute *VarcharAttribute(const char* value) {
    Attribute *attribute = new Attribute;
    attribute->type = kVarchar;
    strcpy(attribute->char_value, value);
    return attribute;
}

/**
 *  Set the value of a Block
 */
void SetValue(Block &block, const char* value) {
    int size = strlen(value) + 1;
    block.size = size;
    block.data = malloc(size);
    memcpy(block.data, value, size);
}

/**
 * Create a record for the BasicTest
 */
Record *CreateRecordBasic(int32_t attribute_1, int64_t attribute_2, const char* attribute_3,
        const char* payload) {
    Attribute** a = new Attribute*[3];

    a[0] = ShortAttribute(attribute_1);
    a[1] = IntAttribute(attribute_2);
    a[2] = VarcharAttribute(attribute_3);

    Record *record = new Record;
    record->key.value = a;
    record->key.attribute_count = 3;
    SetValue(record->payload, payload);

    return record;
}

/**
 *  Compares the 4 attributes used in the Qtree of two records
 */
bool recordCmpQtree(const Record &a, const Record &b) {
    if (keycmp(a.key, b.key) == 0) {
        if (BlockCmp_DataOnly(a.payload, b.payload) == true)
            return true;
        else
            return false;
    } else
        return false;
}

/**
 * Compare the content of a block
 *
 * @return true if blocks are equal
 */
bool BlockCmp_DataOnly(const Block a, const Block b) {
    if (a.size == b.size) {
        //std::cout << strlen((char*) a.data);
        return (strcmp(((char*) (a.data)), ((char*) b.data)) == 0);
    }
    return false;
}

void keycmp_partial(const Key &a, const Key &b, std::vector< int> ls, int s, std::list< int>* l) {
    //  std::list< int> *l;// = new std::list<int>();
    assert(a.attribute_count == b.attribute_count);
    int all = (int) pow((double) 2, s);
    uint32_t difference = 0;
    uint8_t count = (a.attribute_count > 4 ? 4 : a.attribute_count);
    int power = 0;
    int res = 0;
    int final = 0;
    // Compare the keys
    for (int i = 0; i < count; i++) {
        if (a.value[i] != NULL) {
            res = attcmp_(*(a.value[i]), *(b.value[i]));
            power = (int) pow((double) 2, a.attribute_count - 1 - i);
            difference += (res * power);
        }
    }

    for (int j = 0; j < all; j++) {
        int comb = j;
        for (int i = 0; i < s; i++) { //0 is the first dimension
            res = comb % 2;
            comb = (int) comb / 2;
            power = (int) pow((double) 2, a.attribute_count - ls[i] - 1);
            final = (res * power) + difference;
        }
        l->push_back(final);
    }
    //  return l;
}

int recordsCmp_partial(const Key &a, const Key &k) {
    assert(a.attribute_count == k.attribute_count);
    for (int i = 0; i < a.attribute_count; i++) {
        if (a.value[i] != NULL) {
            int res = attcmp(*(a.value[i]), *(k.value[i]));
            if (res < 0)
                return 5;
        }
    }

    return 0;
}

int keycmp_sort(const Key &a, const Key &b) {
    assert(a.attribute_count == b.attribute_count);

    // Compare the keys
    for (int i = 0; i < a.attribute_count; i++) {
        assert(a.value[i] != NULL);
        assert(b.value[i] != NULL);
        int res = attcmp_(*(a.value[i]), *(b.value[i]));
        if (res != 0)
            return res;
    }
    return 0;
}
//Quad Tree Comparing Way bit by bit

//bool keyCheckIsPartial(Key key) 
//{
//	for (int i=0;i<key.attribute_count;i++)
//		switch(key.value[i]->type)
//	{
//		case kShort:
//			{
//				if(key.value[i]->short_value==NULL)
//					return true;
//				break;
//			}
//		case kInt:
//			{
//				if(key.value[i]->int_value==NULL)
//					return true;
//				break;		
//			}
//
//		case kVarchar:
//			{
//				if(key.value[i]->char_value==NULL)
//					return true;
//				break;
//			}	
//	}
//	return false;
//}
//
// Free all resources that are currently used by record
// (this also includes the memory used to store the payload)

void Release(Record* record) {
    if (record) {
        free(record->payload.data);
        for (int i = 0; i < record->key.attribute_count; i++) {
            delete record->key.value[i];
        }
        delete[] record->key.value;
    }
}
// Compare two Records

int RecordCmp(const Record& a, const Record& b) {
    int r = KeyCmp(a.key, b.key);
    if (r == 0) {
        return BlockCmp(a.payload, b.payload);
    }
    return r;
}
// Compare two Keys

int KeyCmp(const Key& a, const Key& b) {
    int r = a.attribute_count - b.attribute_count;
    if (r != 0)
        return r;

    for (int i = 0; i < a.attribute_count; i++) {
        assert(a.value[i]);
        assert(b.value[i]);
        switch (a.value[i]->type) {
            case kShort:
                if (a.value[i]->short_value < b.value[i]->short_value)
                    return -1;
                if (a.value[i]->short_value > b.value[i]->short_value)
                    return 1;
                break;
            case kInt:
                if (a.value[i]->int_value < b.value[i]->int_value)
                    return -1;
                if (a.value[i]->int_value > b.value[i]->int_value)
                    return 1;
                break;
            case kVarchar:
                r = strcmp(a.value[i]->char_value, b.value[i]->char_value);
                if (r != 0)
                    return r;
                break;
            default:
                assert(false);
        }
    }
    return 0;
}

// Create a new record for the RangeTest

Record *CreateRecordRange(int32_t attribute_1, const char* attribute_2, int64_t attribute_3, const char* payload) {
    Attribute** a = new Attribute*[3];

    a[0] = ShortAttribute(attribute_1);
    a[1] = VarcharAttribute(attribute_2);
    a[2] = IntAttribute(attribute_3);

    Record *record = new Record;
    record->key.value = a;
    record->key.attribute_count = 3;
    SetValue(record->payload, payload);

    return record;
}